

#include <unistd.h>
#include <fcntl.h>
#include <syscall.h>
#include <errno.h>
#include <sys/select.h>
#include <signal.h>
#include <stdio.h>
#include "mythread.h"
#include "myaio.h"

#define _GNU_SOURCE

/* Queue to hold threads that are blocked waiting for IO */
mythread_queue_t *blockedQueue = NULL;

/* Used to restore original signal once our read call's work is complete */
struct sigaction old_handler;

/* 1 indicates that a thread is in the kernel, otherwise 0 indicates that the kernel is free */
int kernel_active = 0;
/* 0 indicates no attempt to enter the kernel, otherwise indicates that number of attempts at entering the kernel */
int enter_attempt = 0;
/* Indicates if the handler has been installed before or not */
int initFlag = 0;

/**
 * User-level implementation of the read library function
 *
 *@param fd  file descriptor to read from
 *@param buf  Pointer to buffer where data read should be stored
 *@param nbytes  Number of bytes to read from the file descriptor
 *@return  number of bytes read from the file descriptor
 */
ssize_t read(int fd, void *buf, size_t nbytes){
	/* Set the file descriptor to be asynchronous and nonblocking, print error message if it fails */
	int flags;
	flags = fcntl(fd, F_SETFL, O_ASYNC | O_NONBLOCK);
    if (flags == -1){
		printf("fcntl failed: flags could not be set!----");
	}
	
	/* Set the file descriptor to report back to this process */
	flags = fcntl(fd, F_SETOWN, getpid());
    if (flags == -1){
		printf("fcntl failed: F_SETOWN could not be set!----");
	}

	/* Reset errno so that we can use it to test for EWOULDBLOCK, then call the read system function */
	errno = 0;
	int bufSize = nbytes;
	int bytesRead = syscall(SYS_read, fd, buf, bufSize);
	
	/* If the read fails, call sigaction in mythread_aio_init */
	if(errno == EWOULDBLOCK){
		if(initFlag == 0){
			mythread_aio_init();
			initFlag = 1;
		}
		
		/* Enter the kernel */
		mythread_enter_kernel();
		
		/* Get the current running thread */
		mythread_t oth,nth;
		oth = mythread_self();
		
		/* Block this thread with status of 1, if the queue is null then print an error message */
		mythread_block(blockedQueue, 1);
		if(blockedQueue == NULL){
			printf("Blocked queue is still null----");
		}
		/* Get the new running thread */
		nth = mythread_self();
		
		/* Exit the kernel */
		mythread_exit_kernel();
		
		/* Swap context to new thread now at front of queue, will return here once IO has returned */
		myswapcontext(&(oth->context), &(nth->context));
		
		/* Now call the read because the IO is ready, once read in, then restore original SIGIO signal handler */
		bytesRead = syscall(SYS_read, fd, buf, bufSize);
		
	} else if(bytesRead == -1){
		printf("Read failed, not due to blocking!!----");
		return bytesRead;
	} else{
		/* Once the read is complete, exit the kernel */
		return bytesRead;
	}
	
	/* Return nbytes after successful IO completion after an initial block */
	return bytesRead;
}

/**
 * Initialize the signal handler by using sigaction to set handler for SIGIO
 *
 */
void mythread_aio_init(void){
    /* Set the handler for SIGIO to be mythread_aio_handle and set the handler to block SIGIO while it is operating */
	struct sigaction new_handler;
	new_handler.sa_handler = mythread_aio_handle;
	sigaddset(&new_handler.sa_mask, SIGIO);
	if(sigaction(SIGIO, &new_handler, &old_handler) == -1){
		printf("Sigaction FAILED!----");
	}
	
	/* Initialize the queue that will hold threads blocked for reading, print error message if it fails */
	blockedQueue = (mythread_queue_t *) malloc(sizeof(mythread_queue_t));
	if (blockedQueue == NULL) {
		printf("Unable to create blocked queue; no more memory available\n");
		return 1;
	}
}

/**
 * Handle SIGIO by unblocking a thread once it's IO is ready
 *
 *@param sig  The signal that is being handled
 */
void mythread_aio_handle(int sig){
	/* Try to enter the kernel and if unsuccessful sets flag indicating a need to be called later */
	int success = mythread_tryenter_kernel();
	if(success == 0){
		return;
	} else{
		/* Use select function to test and see if the keyboard is ready for IO */
		fd_set readfds;
		FD_ZERO(&readfds);
		FD_SET(0, &readfds);

        if(select(1, &readfds, NULL, NULL,NULL)){
			/* Unblock the thread blocked for reading and set its status to 0 */
			mythread_unblock(blockedQueue,0);
		}
		
		/* Exit the kernel */
		mythread_exit_kernel();

		return;
	}
}

/**
 * Enter the kernel, setting a flag indicating the kernel is active
 *
 */
void mythread_enter_kernel(void){
	kernel_active = 1;
}

/**
 * Attempts to enter the kernel for SIGIO handling
 *
 * @return 1 indicates that kernel was entered successfully, 0 indicates that entering failed and IO will be handled upon kernel_exit
 */
int mythread_tryenter_kernel(void){
	if(kernel_active==0){
		mythread_enter_kernel();
		return 1;
	}else{
		enter_attempt = 1;
		return 0;
	}
}

/**
 * Exit the kernel, setting a flag indicating the kernel is no longer active.
 * Check to see if the handler has tried to take control and if so then call the signal handler.
 *
 */
void mythread_exit_kernel(void){
	kernel_active = 0;
	
	if(enter_attempt > 0){
		enter_attempt = 0;
		mythread_aio_handle(SIGIO);
	}
}
